`include "common.svh"


module ras (
    input clk,
    input rst,

    input update,
    input [`RAS_AW-1:0] update_pointer,
    input [`WORD_BITS-1:0] update_data,
    input [`RAS_AW:0] update_num,

    input push,
    input [`WORD_BITS-1:0] push_data,
    input pop,

    output [`RAS_AW-1:0] top_pointer,
    output [`WORD_BITS-1:0] top_data,
    output [`RAS_AW:0] top_num,
    output empty

);

  logic [`WORD_BITS-1:0] ras[`RAS_SIZE-1:0];
  logic [`RAS_AW-1:0] top_pointer_next;
  logic [`RAS_AW:0] top_num_next;

  logic [`RAS_AW-1:0] waddr = update ? update_pointer : top_pointer_next;
  wire [`WORD_BITS-1:0] wdata = update ? update_data : push_data;
  wire we = update | push;

  logic full = top_num == `RAS_SIZE;

  always_ff @(posedge clk) begin
    if (we) begin
      ras[waddr] <= wdata;
    end
  end
  reg_r #(`RAS_AW, {(`RAS_AW) {1'b1}}) top_pointer_r (
      .din (top_pointer_next),
      .dout(top_pointer),
      .*
  );
  reg_r #(`RAS_AW + 1, {(`RAS_AW + 1) {1'b1}}) top_num_r (
      .din (top_num_next),
      .dout(top_num),
      .*
  );
  assign empty = top_num == 'b0;
  always_comb begin
    top_pointer_next = top_pointer;
    top_num_next = top_num;
    case ({
      push, pop
    })
      2'b10: begin
        top_pointer_next = top_pointer + 'b1;
        if (!full) begin
          top_num_next = top_num + 'b1;
        end
      end
      2'b01: begin
        if (!empty) begin
          top_pointer_next = top_pointer - 'b1;
          top_num_next = top_num - 'b1;
        end
      end
      default: begin

      end
    endcase
    if (update) begin
      top_pointer_next = update_pointer;
      top_num_next = update_num;
    end
  end

  assign top_data = ras[top_pointer[`RAS_AW-1:0]];


endmodule

module bpu #(
    parameter AW = 1  //block width , 2**AW instructions per block
) (
    input clk,
    input rst,
    /* verilator lint_off UNUSEDSIGNAL */
    input i_valid,
    output i_ready,
    input word_t i_pc,

    input update_valid,
    input word_t update_pc,
    input BPU_Res update_res,
    /* verilator lint_on UNUSEDSIGNAL */
    output o_valid,
    output o_res_valid,
    output BPU_Res o_res,
    output [AW-1:0] o_res_offset,
    output o_res_multi_branch

);
  localparam TH = `WORD_BITS - 1;
  localparam TL = `BTB_AW + 2;
  typedef struct packed {
    logic valid;
    BranchType branch_type;
    logic [TH:TL] tag;
    logic [`WORD_BITS-1:0] target;
  } BTB_Entry;
  wire i_fire = i_valid & i_ready;
  assign i_ready = ~(update_valid | resetting);

  // L1 BTB Reset Logic
  logic resetting, reset_done;
  logic [`BTB_AW:0] resetting_index;
  assign reset_done = resetting_index[`BTB_AW];
  reg_l #(1) resetting_r (
      .load(rst | (reset_done)),
      .din (rst),
      .dout(resetting),
      .*
  );
  reg_lr #(`BTB_AW + 1) resetting_index_r (
      .rst (rst),
      .load(resetting),
      .din (resetting_index + 'b1),
      .dout(resetting_index),
      .*
  );

  //l1btb update logic
  BTB_Entry update_entry;
  assign update_entry.valid = update_res.branch_type != BT_None;
  assign update_entry.tag = update_pc[TH:TL];
  assign update_entry.target = update_res.target;
  assign update_entry.branch_type = update_res.branch_type;

  logic [2**AW-1:0] l1btb_rhit;
  BTB_Entry l1btb_rhit_entry  /*verilator public*/;
  logic [1:0] bi_sc_rhit_entry;
  logic l1btb_rhit_valid;
  logic [`WORD_BITS-1:2] l1btb_rhit_pc;
  logic [AW-1:0] l1btb_rhit_res_index;
  genvar i;
  generate

    wire l1btb_we = resetting | update_valid;
    wire [`BTB_AW-1:0] l1btb_waddr = resetting ? resetting_index[`BTB_AW-1:0] : update_pc[TL-1:TL-`BTB_AW];
    BTB_Entry l1btb_wentry;
    assign l1btb_wentry = resetting ? 'b0 : update_entry;
    BTB_Entry l1btb_rentry[2**AW-1:0];
    logic [1:0] bi_sc_rentry[2**AW-1:0];
    logic [`WORD_BITS-1:2] l1btb_r_pc[2**AW-1:0];
    logic [AW-1:0] i_pc_offset = i_pc[AW+1:2];
    logic [AW-1:0] res_index[2**AW-1:0];
    logic [AW-1:0] i_pc_offset_s1;
    reg_next #(AW) i_pc_offset_s1_r (
        .din (i_pc_offset),
        .dout(i_pc_offset_s1),
        .*
    );

    for (i = 0; i < 2 ** AW; i = i + 1) begin : L1BTB_BANK
      wire [AW-1:0] res_index_next = i - i_pc_offset;
      wire [`WORD_BITS-1:2] r_pc = i_pc[`WORD_BITS-1:2] + {{(`WORD_BITS - AW - 3) {1'b0}}, res_index_next};

      wire [TH:TL] l1btb_rtag = l1btb_r_pc[i][TH:TL];
      reg_next #(`WORD_BITS - 2) l1btb_rtag_r (
          .din (r_pc),
          .dout(l1btb_r_pc[i]),
          .*
      );
      reg_next #(AW) res_index_r (
          .din (res_index_next[AW-1:0]),
          .dout(res_index[i]),
          .*
      );

      BTB_Entry wdata, rdata;
      logic [`BTB_AW-AW-1:0] waddr, raddr;
      logic we, re, cs;
      logic [AW-1:0] wbank;
      ram_1r_1w #($size(BTB_Entry), `BTB_AW - AW) l1btb_ram (.*);
      assign cs = 1'b1;
      assign wdata = l1btb_wentry;
      assign {waddr, wbank} = l1btb_waddr;
      assign we = l1btb_we & (wbank == i);
      assign re = i_fire;
      assign raddr = r_pc[TL-1:AW+2];
      assign l1btb_rentry[i] = rdata;
      assign l1btb_rhit[i] = l1btb_rentry[i].valid & (l1btb_rentry[i].tag == l1btb_rtag);

      logic [`BTB_AW-AW-1:0] bi_sc_raddr;
      reg_next #(`BTB_AW - AW) bi_sc_raddr_r (
          .din (raddr),
          .dout(bi_sc_raddr),
          .*
      );
      // 2-bit Bimodal Counter
      logic [1:0] bi_sc[(2**(`BTB_AW-AW))-1:0];
      logic [1:0] bi_sc_next;
      logic [1:0] bi_sc_update_next;
      assign bi_sc_rentry[i] = bi_sc[bi_sc_raddr];
      always_comb begin
        case (bi_sc_rentry[i])
          2'b00:   bi_sc_next = 2'b00;
          2'b01:   bi_sc_next = 2'b00;
          2'b10:   bi_sc_next = 2'b11;
          2'b11:   bi_sc_next = 2'b11;
          default: bi_sc_next = 2'b00;
        endcase
        bi_sc_update_next = update_res.bi_cnt;
        if (update_res.taken & (update_res.bi_cnt != 2'b11))
          bi_sc_update_next = update_res.bi_cnt + 2'b01;
        if (~update_res.taken & (update_res.bi_cnt != 2'b00))
          bi_sc_update_next = update_res.bi_cnt - 2'b01;
      end

      always_ff @(posedge clk) begin
        if (resetting) begin
          bi_sc[waddr] <= 2'b01;
        end else if ((wbank == i) & update_valid & update_res.branch_type == BT_Branch) begin
          bi_sc[waddr] <= bi_sc_update_next;
        end else if (l1btb_rhit[i] & l1btb_rentry[i].branch_type == BT_Branch) begin
          bi_sc[bi_sc_raddr] <= bi_sc_next;
        end
      end
    end  // for_end
    assign l1btb_rhit_valid = |l1btb_rhit;
    logic [2**AW-1:0] one_hot_sel;
    logic [AW:0] branch_num;
    integer j;
    always_comb begin
      o_res_multi_branch = 1'b0;
      one_hot_sel = l1btb_rhit;
      branch_num = 'b0;
      for (j = 0; j < 2 ** AW; j = j + 1) begin
        if (l1btb_rhit[j]) branch_num = branch_num + 'b1;
      end

      if (branch_num > 1) begin
        one_hot_sel = 'b0;
        one_hot_sel[i_pc_offset_s1] = 1'b1;
        o_res_multi_branch = 1'b1;
      end
    end
    MUX_OH #((2 ** AW), ($size(
        BTB_Entry
    ))) l1btb_rhit_entry_mux (
        .sel (one_hot_sel),
        .din (l1btb_rentry),
        .dout(l1btb_rhit_entry)
    );
    MUX_OH #((2 ** AW), 2) bi_sc_rhit_entry_mux (
        .sel (one_hot_sel),
        .din (bi_sc_rentry),
        .dout(bi_sc_rhit_entry)
    );
    MUX_OH #(((2 ** AW)), (`WORD_BITS - 2)) l1btb_rhit_pc_mux (
        .sel (one_hot_sel),
        .din (l1btb_r_pc),
        .dout(l1btb_rhit_pc)
    );
    MUX_OH #(((2 ** AW)), AW) l1btb_rhit_res_index_mux (
        .sel (one_hot_sel),
        .din (res_index),
        .dout(l1btb_rhit_res_index)
    );

  endgenerate

  BPU_Res bpu_res;

  //RAS
  wire ras_update = update_valid & update_res.ras_valid;
  wire [`RAS_AW-1:0] ras_update_pointer = update_res.ras_pointer;
  wire [`WORD_BITS-1:0] ras_update_data = update_res.ras_top;
  wire [`RAS_AW:0] ras_update_num = update_res.ras_top_num;
  logic ras_push;
  logic ras_pop;
  logic [`WORD_BITS-1:0] ras_push_data, ras_top_data;
  logic [`RAS_AW-1:0] ras_top_pointer;
  logic [`RAS_AW:0] ras_top_num;
  logic ras_empty;
  wire ras_top_valid = ~ras_empty;

  always_comb begin
    ras_push_data = 'b0;
    ras_push = 1'b0;
    ras_pop = 1'b0;
    if (o_res_valid) begin
      case (l1btb_rhit_entry.branch_type)
        BT_Call: begin
          ras_push_data = {l1btb_rhit_pc[`WORD_BITS-1:2], 2'b0};
          ras_push = 1'b1;
          ras_pop = 1'b0;
        end
        BT_Ret: begin
          ras_push_data = 'b0;
          ras_push = 1'b0;
          ras_pop = 1'b1;
        end
        BT_CallRet: begin
          ras_push_data = 'b0;
          ras_push = 1'b0;
          ras_pop = 1'b0;
        end
        default: ;
      endcase
    end
    if (ras_update) begin
      ras_push_data = 'b0;
      ras_push = 1'b0;
      ras_pop = 1'b0;
    end
  end

  ras inst_ras (
      .update(ras_update),
      .update_pointer(ras_update_pointer),
      .update_data(ras_update_data),
      .update_num(ras_update_num),
      .push(ras_push),
      .push_data(ras_push_data),
      .pop(ras_pop),
      .top_pointer(ras_top_pointer),
      .top_data(ras_top_data),
      .top_num(ras_top_num),
      .empty(ras_empty),
      .*
  );


  always_comb begin
    bpu_res = 'b0;
    bpu_res.taken = 1'b1;
    bpu_res.target = l1btb_rhit_entry.target;
    bpu_res.branch_type = l1btb_rhit_entry.branch_type;
    case (l1btb_rhit_entry.branch_type)
      BT_Branch: begin
        bpu_res.taken  = bi_sc_rhit_entry[1];
        bpu_res.bi_cnt = bi_sc_rhit_entry;
      end
      BT_Call, BT_CallRet: begin
        bpu_res.ras_valid = ras_top_valid;
        bpu_res.ras_top = ras_top_data;
        bpu_res.ras_pointer = ras_top_pointer;
      end
      BT_Ret: begin
        if (ras_top_valid) begin
          bpu_res.target = ras_top_data + 'h4;
        end
        bpu_res.ras_valid = ras_top_valid;
        bpu_res.ras_top = ras_top_data;
        bpu_res.ras_top_num = ras_top_num;
        bpu_res.ras_pointer = ras_top_pointer;
      end
      BT_Jump: begin

      end
      default: bpu_res = 'b0;
    endcase
  end
  reg_r #(1) o_valid_r (
      .din (i_fire),
      .dout(o_valid),
      .*
  );
  assign o_res = bpu_res;
  assign o_res_valid = l1btb_rhit_valid & l1btb_rhit_entry.valid;
  assign o_res_offset = l1btb_rhit_res_index;

  // reg_r #($size(BPU_Res)) o_res_r ( .din(bpu_res), .dout(o_res), .*);
  // reg_r #(1) o_res_valid_r ( .din(l1btb_rhit_valid), .dout(o_res_valid), .*);
  // reg_next #(AW) o_res_offset_r (
  //     .din (l1btb_rhit),
  //     .dout(o_res_offset),
  //     .*
  // );

endmodule



